package net.ossrs.yasea;

import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.Toast;

import com.seu.magicfilter.base.gpuimage.GPUImageFilter;
import com.seu.magicfilter.utils.MagicFilterFactory;
import com.seu.magicfilter.utils.MagicFilterType;
import com.seu.magicfilter.utils.OpenGLUtils;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
 * Created by Leo Ma on 2016/2/25.
 */
public class SrsCameraView extends GLSurfaceView implements GLSurfaceView.Renderer {

  private GPUImageFilter magicFilter;

  private SurfaceTexture surfaceTexture;

  private int mOESTextureId = OpenGLUtils.NO_TEXTURE;
  private int mSurfaceWidth;
  private int mSurfaceHeight;
  private int mPreviewWidth;
  private int mPreviewHeight;
  private float mInputAspectRatio;
  private float mOutputAspectRatio;
  private float[] mProjectionMatrix = new float[16];
  private float[] mSurfaceMatrix = new float[16];
  private float[] mTransformMatrix = new float[16];

  private Camera mCamera;
  private ByteBuffer mGlPreviewBuffer;
  private int mCamId = -1;
  private int mPreviewRotation = 90;

  private Thread worker;
  private final Object writeLock = new Object();
  private ConcurrentLinkedQueue<IntBuffer> mGLIntBufferCache = new ConcurrentLinkedQueue<>();
  private PreviewCallback mPrevCb;

  public SrsCameraView(Context context) {
    this(context, null);
  }

  public SrsCameraView(Context context, AttributeSet attrs) {
    super(context, attrs);

    setEGLContextClientVersion(2);
    setRenderer(this);
    setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
  }

  @Override
  public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    GLES20.glDisable(GL10.GL_DITHER);
    GLES20.glClearColor(0, 0, 0, 0);

    magicFilter = new GPUImageFilter(MagicFilterType.NONE);
    magicFilter.init(getContext().getApplicationContext());
    magicFilter.onInputSizeChanged(mPreviewWidth, mPreviewHeight);

    mOESTextureId = OpenGLUtils.getExternalOESTextureID();
    surfaceTexture = new SurfaceTexture(mOESTextureId);
    surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
      @Override
      public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        requestRender();
      }
    });

    // For camera preview on activity creation
    if (mCamera != null) {
      try {
        mCamera.setPreviewTexture(surfaceTexture);
      } catch (IOException ioe) {
        ioe.printStackTrace();
      }
    }
  }

  @Override
  public void onSurfaceChanged(GL10 gl, int width, int height) {
    GLES20.glViewport(0, 0, width, height);
    mSurfaceWidth = width;
    mSurfaceHeight = height;
    magicFilter.onDisplaySizeChanged(width, height);

    mOutputAspectRatio = width > height ? (float) width / height : (float) height / width;
    float aspectRatio = mOutputAspectRatio / mInputAspectRatio;

    Log.i("YASEA-TAG", "onSurfaceChanged width: " + width
        + " height: " + height
        + " mOutputAspectRatio: " + mOutputAspectRatio
        + " \n mPreviewWidth: " + mPreviewWidth
        + " mPreviewHeight: " + mPreviewHeight
        + " mInputAspectRatio: " + mInputAspectRatio
        + " aspectRatio: " + aspectRatio);
    if (width > height) {
      Matrix.orthoM(mProjectionMatrix, 0, -1.0f, 1.0f, -aspectRatio, aspectRatio, -1.0f, 1.0f);
    } else {
      Matrix.orthoM(mProjectionMatrix, 0, -aspectRatio, aspectRatio, -1.0f, 1.0f, -1.0f, 1.0f);
    }
  }

  @Override
  public void onDrawFrame(GL10 gl) {
    GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

    surfaceTexture.updateTexImage();

    surfaceTexture.getTransformMatrix(mSurfaceMatrix);
    Matrix.multiplyMM(mTransformMatrix, 0, mSurfaceMatrix, 0, mProjectionMatrix, 0);
    magicFilter.setTextureTransformMatrix(mTransformMatrix);

    magicFilter.onDrawFrame(mOESTextureId);
    mGLIntBufferCache.add(magicFilter.getGLFboBuffer());
    synchronized (writeLock) {
      writeLock.notifyAll();
    }
  }

  public void setPreviewCallback(PreviewCallback cb) {
    mPrevCb = cb;
  }

  public void setPreviewResolution(int width, int height) {
    mPreviewWidth = width;
    mPreviewHeight = height;
    mGlPreviewBuffer = ByteBuffer.allocate(width * height * 4);
    mInputAspectRatio = width > height ? (float) width / height : (float) height / width;
  }

  public boolean setFilter(final MagicFilterType type) {
    if (mCamera == null) {
      return false;
    }

    queueEvent(new Runnable() {
      @Override
      public void run() {
        if (magicFilter != null) {
          magicFilter.destroy();
        }
        magicFilter = MagicFilterFactory.initFilters(type);
        if (magicFilter != null) {
          magicFilter.init(getContext().getApplicationContext());
          magicFilter.onInputSizeChanged(mPreviewWidth, mPreviewHeight);
          magicFilter.onDisplaySizeChanged(mSurfaceWidth, mSurfaceHeight);
        }
      }
    });
    requestRender();
    return true;
  }

  private void deleteTextures() {
    if (mOESTextureId != OpenGLUtils.NO_TEXTURE) {
      queueEvent(new Runnable() {
        @Override
        public void run() {
          GLES20.glDeleteTextures(1, new int[]{mOESTextureId}, 0);
          mOESTextureId = OpenGLUtils.NO_TEXTURE;
        }
      });
    }
  }

  public void setPreviewRotation(int rotation) {
    mPreviewRotation = rotation;
  }

  public void setCameraId(int id) {
    mCamId = id;
  }

  public int getCameraId() {
    return mCamId;
  }

  public boolean startCamera() {
    if (mCamera != null) {
      return false;
    }
    if (mCamId > (Camera.getNumberOfCameras() - 1)) {
      return false;
    }

    worker = new Thread(new Runnable() {
      @Override
      public void run() {
        while (!Thread.interrupted()) {
          while (!mGLIntBufferCache.isEmpty()) {
            IntBuffer picture = mGLIntBufferCache.poll();
            mGlPreviewBuffer.asIntBuffer().put(picture.array());
            mPrevCb.onGetRgbaFrame(mGlPreviewBuffer.array(), mPreviewWidth, mPreviewHeight);
          }
          // Waiting for next frame
          synchronized (writeLock) {
            try {
              // isEmpty() may take some time, so we set timeout to detect next frame
              writeLock.wait(500);
            } catch (InterruptedException ie) {
              worker.interrupt();
            }
          }
        }
      }
    });
    worker.start();

    if (mCamId < 0) {
      Camera.CameraInfo info = new Camera.CameraInfo();
      int numCameras = Camera.getNumberOfCameras();
      for (int i = 0; i < numCameras; i++) {
        Camera.getCameraInfo(i, info);
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
          mCamera = Camera.open(i);
          mCamId = i;
          break;
        }
      }
    } else {
      mCamera = Camera.open(mCamId);
    }
    if (mCamera == null) {
      mCamera = Camera.open();
      mCamId = 0;
    }

    Camera.Parameters params = mCamera.getParameters();
    Camera.Size size = mCamera.new Size(mPreviewWidth, mPreviewHeight);
    if (!params.getSupportedPreviewSizes().contains(size) || !params.getSupportedPictureSizes().contains(size)) {
      Toast.makeText(getContext(), String.format("Unsupported resolution %dx%d", size.width, size.height), Toast.LENGTH_SHORT).show();
      stopCamera();
      return false;
    }

    List<String> supportedFocusModes = params.getSupportedFocusModes();
    if (!supportedFocusModes.isEmpty()) {
      if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
      } else {
        params.setFocusMode(supportedFocusModes.get(0));
      }
    }

    /***** set parameters *****/
    params.setPictureSize(mPreviewWidth, mPreviewHeight);
    params.setPreviewSize(mPreviewWidth, mPreviewHeight);
    int[] range = findClosestFpsRange(SrsEncoder.VFPS, params.getSupportedPreviewFpsRange());
    params.setPreviewFpsRange(range[0], range[1]);
    params.setPreviewFormat(ImageFormat.NV21);
    params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
    params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
    params.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
    if (!params.getSupportedFocusModes().isEmpty()) {
      params.setFocusMode(params.getSupportedFocusModes().get(0));
    }
    mCamera.setParameters(params);

    mCamera.setDisplayOrientation(mPreviewRotation);

    try {
      mCamera.setPreviewTexture(surfaceTexture);
    } catch (IOException e) {
      e.printStackTrace();
    }
    mCamera.startPreview();

    return true;
  }

  public void stopCamera() {
    if (worker != null) {
      worker.interrupt();
      try {
        worker.join();
      } catch (InterruptedException e) {
        e.printStackTrace();
        worker.interrupt();
      }
      mGLIntBufferCache.clear();
      worker = null;
    }

    if (mCamera != null) {
      mCamera.stopPreview();
      mCamera.release();
      mCamera = null;
    }
  }

  private int[] findClosestFpsRange(int expectedFps, List<int[]> fpsRanges) {
    expectedFps *= 1000;
    int[] closestRange = fpsRanges.get(0);
    int measure = Math.abs(closestRange[0] - expectedFps) + Math.abs(closestRange[1] - expectedFps);
    for (int[] range : fpsRanges) {
      if (range[0] <= expectedFps && range[1] >= expectedFps) {
        int curMeasure = Math.abs(range[0] - expectedFps) + Math.abs(range[1] - expectedFps);
        if (curMeasure < measure) {
          closestRange = range;
          measure = curMeasure;
        }
      }
    }
    return closestRange;
  }

  public interface PreviewCallback {

    void onGetRgbaFrame(byte[] data, int width, int height);
  }
}
